package demoMod.gdxform.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import demoMod.gdxform.abstracts.Container;
import demoMod.gdxform.core.FormManager;
import demoMod.gdxform.interfaces.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class GList<T extends Element> extends Container<GList.ListCellRenderer<T>> implements MouseEventSubscriber, KeyboardEventSubscriber {
    private int columns = 1;
    private float cellWidth = 120.0F;
    private float cellHeight = 30.0F;
    private int lastCellLine = 0;
    private int lastCellColumn = 0;
    private boolean mouseDown = false;
    private Color selectedBgColor = Color.BLUE.cpy();
    private int maxSelectedItems = Integer.MAX_VALUE;
    private final List<GList.ListCellRenderer<T>> selectedItems;

    public GList() {
        setPriority(incr.getAndIncrement());
        this.selectedItems = new ArrayList<>();
    }

    @Override
    public void render(SpriteBatch sb) {
        if (!this.visible()) return;
        if (ListCellRenderer.fbo == null) {
            ListCellRenderer.fbo = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
        }
        ListCellRenderer.fbo.begin();
        Gdx.gl20.glClearColor(0, 0, 0, 0);
        Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
        sb.setColor(Color.WHITE);

        if (this.borderTexture != null) {
            sb.draw(this.borderTexture, getX(true) - getBorderWidth(), getY(true) - getBorderWidth(), getWidth() + getBorderWidth() * 2, getHeight() + getBorderWidth() * 2);
        }
        if (this.bgTexture != null) {
            sb.draw(this.bgTexture, getX(true), getY(true), getWidth(), getHeight());
        }
        sb.flush();
        ListCellRenderer.fbo.end();
        int i = 0; //行
        int j = 0; //列
        for (ListCellRenderer<T> listCellRenderer : getElements()) {
            listCellRenderer.setX(j * cellWidth);
            listCellRenderer.setY(getHeight() - (i + 1) * cellHeight);
            listCellRenderer.render(sb);
            j++;
            if (j >= columns) {
                j = 0;
                i++;
            }
        }

        Texture cellTexture = ListCellRenderer.fbo.getColorBufferTexture();
        getFrameBuffer().begin();
        sb.setColor(Color.WHITE);
        if (getParent() == null) {
            sb.draw(cellTexture,
                    (int) getX(true) - (int) this.getBorderWidth(), (int) getY(true) - (int) this.getBorderWidth(),
                    0, 0,
                    (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                    1, 1,
                    0,
                    (int) getX(true) - (int) this.getBorderWidth(), (int) getY(true) - (int) this.getBorderWidth(),
                    (int) getWidth() + (int) this.getBorderWidth() * 2, (int) getHeight() + (int) this.getBorderWidth() * 2,
                    false, true);
        } else {
            sb.draw(cellTexture,
                    (int) getParent().getSelfX(true) - (int) getParent().getBorderWidth(), (int) getParent().getSelfY(true) - (int) getParent().getBorderWidth(),
                    0, 0,
                    (int) getParent().getWidth() + (int) getParent().getBorderWidth() * 2, (int) getParent().getHeight() + (int) getParent().getBorderWidth() * 2,
                    1, 1,
                    0,
                    (int) getParent().getSelfX(true) - (int) getParent().getBorderWidth(), (int) getParent().getSelfY(true) - (int) getParent().getBorderWidth(),
                    (int) getParent().getWidth() + (int) getParent().getBorderWidth() * 2, (int) getParent().getHeight() + (int) getParent().getBorderWidth() * 2,
                    false, true);
        }
        sb.flush();
        getFrameBuffer().end();
    }

    @Override
    public float getX(boolean isAbsolute) {
        return getParent() != null ? getParent().getX(isAbsolute) : 0;
    }

    @Override
    public float getSelfX(boolean isAbsolute) {
        return getParent() != null ? getParent().getSelfX(isAbsolute) : 0;
    }

    @Override
    public float getY(boolean isAbsolute) {
        if (getParent() instanceof GScrollPane) {
            return getParent().getY(isAbsolute) + ((GScrollPane) getParent()).getPlainHeight() - getHeight();
        }
        return getParent() != null ? getParent().getY(isAbsolute) + getParent().getHeight() - getHeight() : Gdx.graphics.getHeight() - getHeight();
    }

    @Override
    public float getSelfY(boolean isAbsolute) {
        return getParent() != null ? getParent().getSelfY(isAbsolute) : Gdx.graphics.getHeight() - getHeight();
    }

    public float getYWithoutScrollPane(boolean isAbsolute) {
        if (getParent() instanceof GScrollPane) {
            GScrollPane scrollPane = (GScrollPane) getParent();
            return scrollPane.getSelfY(isAbsolute);
        }
        return getParent() != null ? getParent().getY(isAbsolute) + getParent().getHeight() - getHeight() : Gdx.graphics.getHeight() - getHeight();
    }

    @Override
    public float getWidth() {
        return lastCellLine == 0 ? (float)lastCellColumn * cellWidth : (float)columns * cellWidth;
    }

    @Override
    public float getHeight() {
        return lastCellColumn == 0 ? (float)lastCellLine * cellHeight : (float) (lastCellLine + 1) * cellHeight;
    }

    public void addItem(T item) {
        ListCellRenderer<T> listCellRenderer = new ListCellRenderer<>(item);
        listCellRenderer.setWidth(this.cellWidth);
        listCellRenderer.setHeight(this.cellHeight);
        addElement(listCellRenderer);
        lastCellColumn++;
        if (lastCellColumn >= columns) {
            lastCellColumn = 0;
            lastCellLine++;
        }
    }

    public void addItem(int index, T item) {
        ListCellRenderer<T> listCellRenderer = new ListCellRenderer<>(item);
        listCellRenderer.setWidth(this.cellWidth);
        listCellRenderer.setHeight(this.cellHeight);
        int i = 0;
        int p = 0;
        for (ListCellRenderer<T> cellRenderer : getElements()) {
            if (i > index) {
                cellRenderer.setPriority(cellRenderer.getPriority() + 1);
            } else if (i == index) {
                p = cellRenderer.getPriority() + 1;
            }
            i++;
        }
        listCellRenderer.setPriority(p);
        addElement(listCellRenderer);
        lastCellColumn++;
        if (lastCellColumn >= columns) {
            lastCellColumn = 0;
            lastCellLine++;
        }
    }

    public void removeItem(T item) {
        ListCellRenderer<T> toRemove = null;
        for (ListCellRenderer<T> listCellRenderer : getElements()) {
            if (!listCellRenderer.getElements().isEmpty() && listCellRenderer.getElements().get(0) == item) {
                toRemove = listCellRenderer;
                lastCellColumn--;
                if (lastCellColumn < 0) {
                    lastCellColumn = 0;
                    lastCellLine--;
                }
                break;
            }
        }
        if (toRemove != null) {
            removeElement(toRemove);
        }
    }

    public void clear() {
        clearElements();
        lastCellColumn = 0;
        lastCellLine = 0;
    }

    public T getItem(int index) {
        if (getElements().size() <= index) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        return getElements().get(index).getElements().get(0);
    }

    public int indexOf(T item) {
        int index = 0;
        for (ListCellRenderer<T> listCellRenderer : getElements()) {
            if (!listCellRenderer.getElements().isEmpty() && listCellRenderer.getElements().get(0) == item) {
                return index;
            }
            index++;
        }
        return -1;
    }

    public int getColumns() {
        return columns;
    }

    public void setColumns(int columns) {
        if (columns < 1) columns = 1;
        this.columns = columns;
        this.lastCellLine = getElements().size() / columns;
        this.lastCellColumn = getElements().size() % columns;
    }

    public void setSelectedBgColor(Color selectedBgColor) {
        this.selectedBgColor = selectedBgColor;
    }

    public float getCellWidth() {
        return cellWidth;
    }

    public void setCellWidth(float cellWidth) {
        this.cellWidth = cellWidth;
        for (ListCellRenderer<T> listCellRenderer : getElements()) {
            listCellRenderer.setWidth(cellWidth);
        }
    }

    public float getCellHeight() {
        return cellHeight;
    }

    public void setCellHeight(float cellHeight) {
        this.cellHeight = cellHeight;
        for (ListCellRenderer<T> listCellRenderer : getElements()) {
            listCellRenderer.setHeight(cellHeight);
        }
    }

    public List<T> getSelectedItems() {
        return this.selectedItems.stream().map(cellRenderer -> cellRenderer.getElements().isEmpty() ? null : cellRenderer.getElements().get(0)).collect(Collectors.toList());
    }

    public void setSelected(int index, boolean selected) {
        ListCellRenderer<T> listCellRenderer = getElements().get(index);
        if (selected != listCellRenderer.isSelected()) {
            if (selected) {
                this.selectedItems.add(listCellRenderer);
            } else {
                this.selectedItems.remove(listCellRenderer);
            }
        }
        listCellRenderer.setSelectedBgColor(this.selectedBgColor);
        listCellRenderer.setSelected(selected);
    }

    public boolean isSelected(int index) {
        return getElements().get(index).isSelected();
    }

    public boolean isSelected(T item) {
        return getElements().get(indexOf(item)).isSelected();
    }

    public int getMaxSelectedItems() {
        return maxSelectedItems;
    }

    public void setMaxSelectedItems(int maxSelectedItems) {
        this.maxSelectedItems = maxSelectedItems;
    }

    @Override
    public boolean keyDown(int keyCode) {
        if (!(this.visible() || this.enabled())) return false;
        if (this.activated) {
            List<ListCellRenderer<T>> elements = getElements();
            for (int i = elements.size() - 1; i >= 0; i--) {
                ListCellRenderer<T> element = elements.get(i);
                if (element.keyDown(keyCode)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean keyUp(int keyCode) {
        if (!(this.visible() || this.enabled())) return false;
        if (this.activated) {
            List<ListCellRenderer<T>> elements = getElements();
            for (int i = elements.size() - 1; i >= 0; i--) {
                ListCellRenderer<T> element = elements.get(i);
                if (element.keyUp(keyCode)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean keyTyped(char key) {
        if (!(this.visible() || this.enabled())) return false;
        if (this.activated) {
            List<ListCellRenderer<T>> elements = getElements();
            for (int i = elements.size() - 1; i >= 0; i--) {
                ListCellRenderer<T> element = elements.get(i);
                if (element.keyTyped(key)) {
                    return true;
                }
            }
        }
        return false;
    }

    private Element currentResizingElement = null;

    @Override
    public boolean clickDown(int screenX, int screenY, int button) {
        if (!(this.visible() || this.enabled())) return false;
        mouseDown = screenX >= getX(true) && screenX <= getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - screenY >= getY(true) && Gdx.graphics.getHeight() - screenY <= getY(true) + getHeight();
        boolean hasResponse = false;

        List<ListCellRenderer<T>> elements = getElements();
        for (int i=elements.size() - 1;i >= 0;i--) {
            ListCellRenderer<T> element = elements.get(i);
            if (element.isResizable()) {
                if (element.atBorder(screenX, Gdx.graphics.getHeight() - screenY)) {
                    currentResizingElement = element;
                    return true;
                }
            }
            if (element.clickDown(screenX, screenY, button) && !hasResponse) {
                if (!(Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT))) {
                    for (ListCellRenderer<T> listCellRenderer : this.selectedItems) {
                        listCellRenderer.setSelected(false);
                    }
                    this.selectedItems.clear();
                }
                element.setSelectedBgColor(this.selectedBgColor);
                element.setSelected(!element.isSelected());
                if (element.isSelected()) {
                    if (this.selectedItems.size() < this.maxSelectedItems) {
                        this.selectedItems.add(element);
                    } else {
                        element.setSelected(false);
                    }
                } else {
                    this.selectedItems.remove(element);
                }
                hasResponse = true;
            }
        }
        FormManager.getInstance().getEventHooks().setCurrentFocusedElement(this);

        return hasResponse;
    }

    @Override
    public boolean clickUp(int screenX, int screenY, int button) {
        if (!(this.visible() || this.enabled())) return false;
        if (mouseDown) {
            mouseDown = false;
            currentResizingElement = null;

            List<ListCellRenderer<T>> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                ListCellRenderer<T> element = elements.get(i);
                if (element.clickUp(screenX, screenY, button)) {
                    break;
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public boolean mouseDragged(int screenX, int screenY) {
        if (!(this.visible() || this.enabled())) return false;
        List<ListCellRenderer<T>> elements = getElements();
        for (int i=elements.size() - 1;i >= 0;i--) {
            ListCellRenderer<T> element = elements.get(i);
            if (element.mouseDragged(screenX, screenY)) {
                return true;
            }
        }
        if (currentResizingElement != null) {
            ((ResizeEventSubscriber) currentResizingElement).resize(Gdx.input.getDeltaX(), Gdx.input.getDeltaY(), false);
        }
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        if (!(this.visible() || this.enabled())) return false;
        if (screenX >= getX(true) && screenX <= getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - screenY >= getY(true) && Gdx.graphics.getHeight() - screenY <= getY(true) + getHeight()) {
            List<ListCellRenderer<T>> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                ListCellRenderer<T> element = elements.get(i);
                if (element.mouseMoved(screenX, screenY)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        if (!(this.visible() || this.enabled())) return false;
        if (Gdx.input.getX() >= getX(true) && Gdx.input.getX() <= getX(true) + getWidth() &&
                Gdx.graphics.getHeight() - Gdx.input.getY() >= getY(true) && Gdx.graphics.getHeight() - Gdx.input.getY() <= getY(true) + getHeight()) {
            List<ListCellRenderer<T>> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                ListCellRenderer<T> element = elements.get(i);
                if (element.scrolled(amount)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean moveToElementBorder(Element element) {
        return false;
    }

    @Override
    public void activate() {
        super.activate();
        this.setSelectedBgColor(Color.BLUE.cpy());
        for (ListCellRenderer<T> listCellRenderer : selectedItems) {
            listCellRenderer.setSelectedBgColor(this.selectedBgColor);
            listCellRenderer.setSelected(true);
        }
    }

    @Override
    public void deactivate() {
        super.deactivate();
        this.setSelectedBgColor(Color.GRAY.cpy());
        for (ListCellRenderer<T> listCellRenderer : selectedItems) {
            listCellRenderer.setSelectedBgColor(this.selectedBgColor);
            listCellRenderer.setSelected(true);
        }
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (enabled) {
            this.setSelectedBgColor(Color.BLUE.cpy());
        } else {
            this.setSelectedBgColor(Color.GRAY.cpy());
        }
        for (ListCellRenderer<T> listCellRenderer : selectedItems) {
            listCellRenderer.setSelectedBgColor(this.selectedBgColor);
            listCellRenderer.setSelected(true);
        }
    }

    public static class ListCellRenderer<T extends Element> extends Container<T> implements MouseEventSubscriber, KeyboardEventSubscriber {
        private Color selectedBgColor = Color.BLUE.cpy();
        private boolean selected = false;
        private boolean mouseDown = false;
        private int priority = 0;
        private static FrameBuffer fbo;

        public ListCellRenderer(T element) {
            this();
            addElement(element);
            if (fbo == null) {
                fbo = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
            }
        }

        public ListCellRenderer() {
            this.setPriority(incr.getAndIncrement());
        }

        @Override
        public void render(SpriteBatch sb) {
            if (!this.visible()) return;
            fbo.begin();
            sb.setColor(Color.WHITE);

            if (this.bgTexture != null) {
                sb.draw(this.bgTexture, getX(true), getY(true), getWidth(), getHeight());
            }
            sb.flush();
            fbo.end();

            for (Element element : getElements()) {
                if (!(element instanceof Container)) {
                    fbo.begin();
                }
                element.render(sb);
                sb.flush();
                if (!(element instanceof Container)) {
                    fbo.end();
                }
            }
        }

        @Override
        public void addElement(T element) {
            if (getElements().isEmpty()) {
                super.addElement(element);
                return;
            }
            throw new IllegalStateException("A ListCellRenderer can only store one element.");
        }

        public void setSelectedBgColor(Color selectedBgColor) {
            this.selectedBgColor = selectedBgColor;
        }

        public boolean isSelected() {
            return selected;
        }

        public void setSelected(boolean selected) {
            this.selected = selected;
            if (selected) {
                setBackground(this.selectedBgColor);
            } else {
                setBackground(Color.CLEAR.cpy());
            }
        }

        @Override
        public boolean keyDown(int keyCode) {
            if (!(this.visible() || this.enabled())) return false;
            if (this.activated) {
                List<T> elements = getElements();
                for (int i = elements.size() - 1; i >= 0; i--) {
                    T element = elements.get(i);
                    if (element instanceof KeyboardEventSubscriber) {
                        if (((KeyboardEventSubscriber) element).keyDown(keyCode)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        @Override
        public boolean keyUp(int keyCode) {
            if (!(this.visible() || this.enabled())) return false;
            if (this.activated) {
                List<T> elements = getElements();
                for (int i = elements.size() - 1; i >= 0; i--) {
                    T element = elements.get(i);
                    if (element instanceof KeyboardEventSubscriber) {
                        if (((KeyboardEventSubscriber) element).keyUp(keyCode)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        @Override
        public boolean keyTyped(char key) {
            if (!(this.visible() || this.enabled())) return false;
            if (this.activated) {
                List<T> elements = getElements();
                for (int i = elements.size() - 1; i >= 0; i--) {
                    T element = elements.get(i);
                    if (element instanceof KeyboardEventSubscriber) {
                        if (((KeyboardEventSubscriber) element).keyTyped(key)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private Element currentResizingElement = null;

        @Override
        public boolean clickDown(int screenX, int screenY, int button) {
            if (!this.visible()) return false;
            mouseDown = screenX >= getX(true) && screenX <= getX(true) + getWidth() &&
                    Gdx.graphics.getHeight() - screenY >= getY(true) && Gdx.graphics.getHeight() - screenY <= getY(true) + getHeight();

            List<T> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                T element = elements.get(i);
                if (element instanceof Border && element.isResizable()) {
                    if (((Border) element).atBorder(screenX, Gdx.graphics.getHeight() - screenY)) {
                        currentResizingElement = element;
                        return true;
                    }
                }
                if (element instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element).clickDown(screenX, screenY, button)) {
                        return true;
                    }
                }
            }
            FormManager.getInstance().getEventHooks().setCurrentFocusedElement(this);

            return mouseDown;
        }

        @Override
        public boolean clickUp(int screenX, int screenY, int button) {
            if (!this.visible()) return false;
            if (mouseDown) {
                mouseDown = false;
                currentResizingElement = null;
                List<T> elements = getElements();
                for (int i=elements.size() - 1;i >= 0;i--) {
                    T element = elements.get(i);
                    if (element instanceof MouseEventSubscriber) {
                        if (((MouseEventSubscriber) element).clickUp(screenX, screenY, button)) {
                            break;
                        }
                    }
                }
                return true;
            }
            return false;
        }

        @Override
        public boolean mouseDragged(int screenX, int screenY) {
            if (!this.visible()) return false;
            List<T> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                T element = elements.get(i);
                if (element instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element).mouseDragged(screenX, screenY)) {
                        return true;
                    }
                }
            }
            if (currentResizingElement != null) {
                ((ResizeEventSubscriber) currentResizingElement).resize(Gdx.input.getDeltaX(), Gdx.input.getDeltaY(), false);
            }
            return false;
        }

        @Override
        public boolean mouseMoved(int screenX, int screenY) {
            if (!this.visible()) return false;
            if (screenX >= super.getX(true) && screenX <= super.getX(true) + getWidth() &&
                    Gdx.graphics.getHeight() - screenY >= super.getY(true) && Gdx.graphics.getHeight() - screenY <= super.getY(true) + getHeight()) {
                List<T> elements = getElements();
                for (int i=elements.size() - 1;i >= 0;i--) {
                    T element = elements.get(i);
                    if (element instanceof MouseEventSubscriber) {
                        if (((MouseEventSubscriber) element).mouseMoved(screenX, screenY)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        @Override
        public boolean scrolled(int amount) {
            if (!(this.visible() || this.enabled())) return false;
            if (Gdx.input.getX() >= super.getX(true) && Gdx.input.getX() <= super.getX(true) + getWidth() &&
                    Gdx.graphics.getHeight() - Gdx.input.getY() >= super.getY(true) && Gdx.graphics.getHeight() - Gdx.input.getY() <= super.getY(true) + getHeight()) {
                List<T> elements = getElements();
                for (int i=elements.size() - 1;i >= 0;i--) {
                    T element = elements.get(i);
                    if (element instanceof MouseEventSubscriber) {
                        if (((MouseEventSubscriber) element).scrolled(amount)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        @Override
        public boolean moveToElementBorder(Element element) {
            if (!this.visible()) return false;
            List<T> elements = getElements();
            for (int i=elements.size() - 1;i >= 0;i--) {
                T element1 = elements.get(i);
                if (element1 instanceof MouseEventSubscriber) {
                    if (((MouseEventSubscriber) element1).moveToElementBorder(element)) {
                        return true;
                    }
                }
            }
            return false;
        }

        @Override
        public int getPriority() {
            return priority;
        }

        @Override
        public void setPriority(int priority) {
            this.priority = priority;
        }
    }
}
